/**
 \file firmware.pde
 \brief Domino (OpenDomo for Arduino)
 ****************************************************************************
 *  This file is part of the OpenDomo project.
 *  Copyright(C) 2011 OpenDomo Services S.L.
 *
 *  Oriol Palenzuela Roses <opalenzuela (at) opendomo (dot) com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************
 \mainpage Domino
 Domino is a multi-purpose driver, designed for creating quick prototypes and
 automated products using a normalized configuration syntax.

 A detailed explanation of all the supported commands and architecture can be
 found in the following webpages:

 -# http://es.opendomo.org/domino (Main page)
 -# http://es.opendomo.org/domino_reference (Reference manual)

 This document is only intended to be useful for the firmware's programmers.
*/

/// Current library version
#define VERSION "0.4.2"
// Library for serial communication (required by Ethernet.h)
#include <SPI.h>
// Library for managing the configuration storage
#include <EEPROM.h>
// Library for using an Ethernet Shield as host connection
#include <Server.h>
#include <Client.h>
#include <Ethernet.h>
// Library for putting the Arduino in sleep mode
#include <avr/sleep.h>

/** \page EEPROM EEPROM Map
 The EEPROM in Arduino Duemilanove with ATMEGA328 has 1024Bytes availables.
 Domino uses this space to store all the configuration variables required,
 organized in the following segments:

 -# Ports configuration: starting from the first byte (\ref EMPORTSOFFSET), with
	20 Bytes (\ref EMPORTSLOT) for each port, it contains the alias and type of
	each port.
 -# Links: starting after the ports segment (\ref EMLINKSOFFSET) contains the
	index of two ports and a third byte for the type.
 -# Virtual port information: starting from \ref EMVPORTOFFSET, contains the
	data of extended ports (virtual and grouped ports)
 -# Network information: starting from \ref EMNETCFOFFSET, contains 20 bytes
	with the network information (MAC(6)+IP(4)+GW(4)+MASK(4))
 -# Board information: starting in \ref EMBOARDOFFSET contains some information
        about the board, like the name
 -# On-board functions: in \ref EMFUNCSOFFSET contains software-programmable
	\ref Func embedded in the board

*/

#define EW(pos,val) if(val!=EEPROM.read(pos))EEPROM.write(pos,val);
#define EWI(pos,val) EW(pos,val); EW(pos+1,0);
#define ER(pos) EEPROM.read(pos)
#define ERI(pos) EEPROM.read(pos)

#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
	/// Arduino Mega board
#define PINCOUNT  70		//!< Total number of pins (14 digital + 6 analogic)
#define DIGITALPORTS 54		//!< Digital ports count
#define ANALOGPORTS  16		//!< Analog ports count
#define VIRTUALPORTS 10		//!< Virtual ports count
#define MAXLINKS  100		//!< Maximum number of links
#define FUNCSPACE 1000		//!< Total space for on-board functions
	/// EEPROM Memory distribution
#define EMPORTSOFFSET 0		//!< Bytes 0000-1399 Ports configuration
#define EMVPORTOFFSET 1400	//!< Bytes 1400-2299 Virtual ports
#define EMLINKSOFFSET 2300	//!< Bytes 2300-2999 Links
#define EMNETCFOFFSET 3000	//!< Bytes 3000-3020 Network: MAC(6)+IP(4)+GW(4)+MASK(4)
#define EMBOARDOFFSET 3050	//!< Bytes 3050-3099 Additional board information (name)
#define EMFUNCSOFFSET 3100	//!< Bytes 3100-4100 On-board functions
#define EMSEGMENTS 200		//!< Number of segments in \ref EEPROM
//#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__)
#else
	// Arduino Duemilanove and Uno boards
#define PINCOUNT  20		//!< Total number of pins (14 digital + 6 analogic)
#define DIGITALPORTS 14		//!< Number of digital ports
#define ANALOGPORTS   6		//!< Number of analog ports
#define VIRTUALPORTS  5		//!< Number of virtual ports
#define MAXLINKS  10		//!< Maximum number of links
#define FUNCSPACE 100		//!< Total space for on-board functions
	// EEPROM Memory distribution
#define EMPORTSOFFSET 0		//!< \ref EEPROM Bytes 000-399 Ports configuration
#define EMVPORTOFFSET 400	//!< \ref EEPROM Bytes 400-499 Virtual ports
#define EMLINKSOFFSET 500	//!< \ref EEPROM Bytes 500-699 Links
#define EMNETCFOFFSET 800	//!< \ref EEPROM Bytes 800-820 Network: MAC(6)+IP(4)+GW(4)+MASK(4)
#define EMBOARDOFFSET 850	//!< \ref EEPROM Bytes 850-899 Additional board information (name)
#define EMFUNCSOFFSET 900	//!< \ref EEPROM Bytes 900-999 On-board functions
#define EMSEGMENTS 50		//!< Number of segments in \ref EEPROM
#endif

#define EMPORTSLOT 20		//!< Reserved bytes for each port
#define DELAYCYCLE 100		//!< Delay in each cycle, in milliseconds
#define BUFFERSIZE 20		//!< Maximum lenght for the command
#define MAXCHANGES 6		//!< Maximum number of changes allowed per second

/// Encoded values
#define ANALOG  65		//!< Alias for ANALOG    ! ANALOG  DIGITAL
#define DIGITAL 68		//!< Alias for DIGITAL --+----------------
#define IN      73		//!< Alias for INPUT     !   a       d
#define OUT     79		//!< Alias for OUTPUT    !   A       D
#define OFF      0		//!< Alias for OFF
#define ON    HIGH		//!< Alias for ON

#define LISTENINGPORT 23	//!< TCP listening port (see \ref Ethernet Shield)

//! Iterate for each link
#define FOREACHLINK(i)  for (i=0;i<MAXLINKS;i++)
//! Delete the specified link
#define emptyLink(i) {links[i][0]=0; links[i][1]=0; links[i][2]=0;}
//! Total amount of ports
#define TOTALPORTS (DIGITALPORTS+ANALOGPORTS+VIRTUALPORTS)

// This macros will make a little easier to work with the memory structures
//! Iterate for each port
#define FOREACHPORT(i)  for(i=0;i<TOTALPORTS;i++)
//! Iterate for each virtual port
#define FOREACHVPORT(i) for(i=DIGITALPORTS+ANALOGPORTS;i<TOTALPORTS;i++)
//! Return TRUE if the specified port is enabled
//#define portenabled(n)  (porttype[n]!=0)
//! Return TRUE if the specified port is an INPUT port
#define ISINPUT(n)   ((ports[n].type=='a')||(ports[n].type=='d')||(ports[n].type=='p'))
//! Return TRUE if the specified port is an OUTPUT port
#define ISOUTPUT(n)  ((ports[n].type=='A')||(ports[n].type=='D')||(ports[n].type=='P'))
//! Return TRUE if the specified port is an ANALOG port
#define ISANALOG(n)  ((ports[n].type=='a')||(ports[n].type=='A'))
//! Return TRUE if the specified port is a DIGITAL port
#define ISDIGITAL(n) ((ports[n].type=='d')||(ports[n].type=='D'))
//! Return TRUE if the specified port is a PULSE port
#define ISPULSE(n)   ((ports[n].type=='p')||(ports[n].type=='P'))
//! Return TRUE if the specified port is virtual
#define ISVIRTUAL(n) ((n>ANALOGPORTS+DIGITALPORTS) && (ports[n].type='v'))
//! Return TRUE if the specified port is disabled
#define ISDISABLED(n) ((ports[n].type=='x')||(ports[n].type=='X'))

// This functions will help with the string processing
//! Return TRUE if word has the 3 specified characters
#define SPELL3(word,c1,c2,c3) ((word[0]==c1) && (word[1]==c2) && (word[2]==c3))
//! Copy the contents of the EEPROM specified region into the string
#define EEPROM2string(offset,lenght,string) for(i=0;i<lenght;i++) \
	{string[i]=EEPROM.read(offset+i);}

// lnk puls1 alarm
// 012345678901234
//! Copy the first argument into the string
#define GETARG1(str,arg1) arg1[0]=str[4];arg1[1]=str[5];arg1[2]=str[6];\
	arg1[3]=str[7];arg1[4]=str[8];arg1[5]=0;
//! Copy the second argument into the string
#define GETARG2(str,arg2) arg2[0]=str[10];arg2[1]=str[11];arg2[2]=str[12];\
	arg2[3]=str[13];arg2[4]=str[14];arg2[5]=0;
//! Return TRUE if the 6 first bytes of the specified strings are equal
#define strcmp6(s1,s2) ((s1[0]==s2[0]) && (s1[1]==s2[1]) && \
	(s1[2]==s2[2]) && (s1[3]==s2[3]) && (s1[4]==s2[4]) && (s1[5]==s2[5]))
//! Return TRUE if the byte is a valid character (a-z, A-Z and 0-9)
#define ISVALIDCHAR(a) ((a>=48 && a<=57) || (a>=65 && a<=90) || (a>=97 && a<=122))

#ifdef NO_PDE
extern "C" void __cxa_pure_virtual()
{
}
#endif

//! Ranges structure for analog ports
struct portRanges {
	int alarmMax;		///< Alarm maximum level
	int warningMax;		///< Warning maximum level
	int warningMin;		///< Warning minimum level
	int alarmMin;		///< Alarm minimum level
	byte tolerance;		///< Tolerance of the port
	int fxA;
	int fxB;
};

//! Data structure for ports
static struct portStruct {
	byte type;		///< Type of the port (a, d, A, D, v, ...)
	int value;		///< Last value of the port
	byte changes;		///< Number of changes in the last second
	struct portRanges *range;	///< Ranges (only for analog ports
} ports[TOTALPORTS];

#ifdef NO_PDE
#include "firmware.h"
#else
#include "domino.h"
#endif

//! Board name
char bname[6];
//! Link table (3 bytes: port1, port2 and type)
byte links[MAXLINKS][3];
//! Last link created or referenced
byte lastlink = 0;

//! Network server instance
Server server = Server(LISTENINGPORT);
//! Network web server instance
Server webserver = Server(80);
//! This flag determines if the network is enabled or not
byte network = 0;

//! Possible outputs for a \ref printf
enum outputChannel {
	NONE = 0,
	SERIALPORT = 1,
	TELNET = 2,
	HTTP = 3
} output;

#ifdef MODULE_WEBSERVER
#include "webserver.h"
#endif
#ifdef MODULE_ROUTINES
#include "routines.h"
#endif
#ifdef MODULE_IR
#include "ir.h"
#endif

char instruction[BUFFERSIZE];	///< Command received
byte inspos = 0;		///< Command byte position
byte echo = 0;			///< Flag that defines if the local echo is activated
byte debug = 0;			///< Flag that defines if the board operates in debug mode
byte alarmport = 0;		///< Alarm port configured (0 if none)
byte pulseport = 0;		///< Pulse port configured (0 if none)
int loops = 0;			///< Number of loops

int regP = 0;			///< Program pointer for \ref Func

///! Seconds in a day
#define DAYLENGHT 86400
unsigned long time = 0;		///< Internal variable for measuring time
unsigned int days = 0;		///< Uptime days
unsigned int seconds = 0;	///< Uptime seconds (after a day, it rolls back to zero)

///! Event types
enum eventType {
	DEBUG = 68,
	NOTICE = 78,
	WARNING = 87,
	ALARM = 65,
	ERROR = 69,
	INFO = 73
};

/// Printf function (int, char, char)
inline void printf(int ev, char *a, char *b)
{
	printf(ev, a, b, "");
}

/// Printf function overloaded to match all the cases
inline void printf(int ev, char *a)
{
	printf(ev, a, "", "");
}

/// Printf function with integers (int, char, int)
void printf(int ev, char *a, int b)
{
	char strb[10];
	itoan(b, strb, 5);
	printf(ev, a, strb, "");
}

/// Printf function with integers (int, char, char, int)
void printf(int ev, char *a, char *b, int c)
{
	char strc[10];
	itoan(c, strc, 5);
	printf(ev, a, b, strc);
}

/// Printf function with integers (int, char, char, int, int)
void printf(int ev, char *a, int b, int c)
{
	char strb[10];
	char strc[10];
	itoan(b, strb, 5);
	itoan(c, strc, 5);
	printf(ev, a, strb, strc);
}

/** this function is the actual PRINTF
 \param ev Event type
 \param a Level of the event (D/N/W/E/I)
 \param b Element that generates the event
 \param c 3rd string, usually event description
 */
void printf(int ev, char *a, char *b, char *c)
{
	//char *ret;
	//byte pos=0;
	//byte len=30;
	if (debug != 1 && ev == DEBUG)
		return;		// ommit debugging messages
	/*ret = (char*) malloc (len);
	   for (int i=0;i<len;i++) ret[i]=0;
	   if (a!=NULL){ strncat(ret,len,strlen(a),a);}
	   if (b!=NULL){ strncat(ret,len,strlen(b),b);}
	   if (c!=NULL){ strncat(ret,len,strlen(c),c);}
	   if (d!=NULL){ strncat(ret,len,strlen(d),d);}
	   if (e!=NULL){ strncat(ret,len,strlen(e),e);} */
	/// @todo: Send at once (re-enable the previous function)
	switch (output) {
	case SERIALPORT:
		Serial.print(char (ev));
		Serial.print(":");
		Serial.print(bname);
		Serial.print(".");
		Serial.print(a);
		Serial.print(b);
		Serial.println(c);
		//Serial.println(ret);
		break;
	case TELNET:
		server.print(char (ev));
		server.print(":");
		server.print(bname);
		server.print(".");
		server.print(a);
		server.print(b);
		server.println(c);
		break;
	case HTTP:
		if (ev != INFO)
			return;	// Web server only print information, not events
		webserver.print("<li>");
		webserver.print(a);
		webserver.print(" ");
		webserver.print(b);
		webserver.print(" ");
		webserver.print(c);
		webserver.println("</li>");
		break;
	default:
		break;
	}
	//free(ret);
}

/// Print the board version to the standard output
int printVersion()
{
	printf(INFO, "versn ", VERSION);
	printf(INFO, "bdate ", __DATE__);
	printf(INFO, "build ", __TIME__);
}

/// this function will return the number of bytes currently free in RAM
int memoryTest()
{
	int bc = 0;		// initialize a counter
	byte *array;		// create a pointer to a byte array
	while ((array = (byte *) malloc(bc * sizeof(byte))) != NULL) {
		bc++;		// if allocation was successful, then continue
		free(array);	// free memory after allocating it
	}
	free(array);		// also free memory after the function finishes
	return bc;		// send back the highest number of bytes successfully allocated
}

/** Copies a string from one variable to another
 \param dest Destination string
 \param len Length of the string to be copied
 \param src Source string
*/
void strncpy(char *dest, int len, char *src)
{
	byte i;
	for (i = 0; i < len; i++)
		dest[i] = src[i];
	dest[i] = 0;
}

/** Concats a string from one variable to another
 \param dest Destination string
 \param maxlen Max lenght of the destination string
 \param len Length of the source string
 \param src Source string
*/
boolean strncat(char *dest, int maxlen, int len, char *src)
{
	byte i;
	byte p;
	for (i = 0; i < maxlen; i++) {
		if (dest[i] == 0) {
			p = i;
			i = maxlen;	// Exit the loop
		}
	}
	for (i = 0; i < len; i++) {
		if (i + p > maxlen)
			return -1;
		dest[i + p] = src[i];
		dest[i + p + 1] = 0;
	}
	return true;
}

/** Transforms a 5 bytes string into its integer value
 \param str Source string
 \return The integer value
*/
int atoi(char *str)
{
	int i;
	int retval = 0;
	for (i = 0; i < 5; i++) {
		if (str[i] > 47 && str[i] < 58)
			retval = retval * 10 + (str[i] - 48);
		else
			return retval;
	}
	return retval;
}

/** Transforms a integer into a string with lenght LEN
 \param val Integer value
 \param result String where the result will be written
 \param len Lenght of the string
 \return true if the operation was succeeded
*/
int itoan(int val, char *result, int len)
{
	int i = val;
	int c = 0;

	for (i = 0; i < len; i++)
		result[i] = '0';	//Rellenamos con ceros

	while (i > 0) {
		c++;
		i /= 10;
	}

	if (c > len)
		return false;	// Too less bytes to represent

	result[len] = 0;
	c = len;
	i = val;
	while (i > 0) {
		int digit = i - 10 * (i / 10);
		i /= 10;
		result[--c] = digit + 48;
	}
	return true;
}

/** Transforms a 2 bytes hexadecimal string into its integer value
  Example: h2d(E,F)=239
 \param a First byte of the source string
 \param b Second byte of the source string
 \return The integer value
*/
byte h2d(char a, char b)
{
	if (a >= 65 && a <= 70)
		a = a - 55;
	else
		a = a - 48;
	if (b >= 65 && b <= 70)
		b = b - 55;
	else
		b = b - 48;
	return (a * 16 + b);
}

/// This is the callback function for waking up the Arduino
void wakeUp()
{
	// Nothing to be done
}

/// This function will put the Arduino to sleep
void goToSleep()
{
	set_sleep_mode(SLEEP_MODE_PWR_DOWN);
	sleep_enable();

	if (ISINPUT(2))
		attachInterrupt(0, wakeUp, LOW);	// Pin 2
	if (ISINPUT(3))
		attachInterrupt(1, wakeUp, LOW);	// and 3 will wake up the system

	sleep_mode();
	sleep_disable();

	if (ISINPUT(2))
		detachInterrupt(0);
	if (ISINPUT(3))
		detachInterrupt(1);
}

/** Retrieves the requested port name into a string
 \param portid ID of the requested port
 \param name string where the name will be copied
 \return true if the port was found, otherwise false
*/
int getPortName(byte portid, char *name)
{
	if (portid < TOTALPORTS) {
		name[0] = ER(portid * EMPORTSLOT);
		name[1] = ER(portid * EMPORTSLOT + 1);
		name[2] = ER(portid * EMPORTSLOT + 2);
		name[3] = ER(portid * EMPORTSLOT + 3);
		name[4] = ER(portid * EMPORTSLOT + 4);
		name[5] = 0;
		return true;
	} else {
		return false;
	}
}

int setPortValue(int i, int value);

/** This function triggers an event if a port value has changed.
 \param port Port number
 \param ov Old value of the port
 \param nv New value of the port
*/
void triggerPortChange(byte port, byte ov, byte nv)
{
	//byte i;
	char pname[6];
	if (ov == nv)
		return;		// Value didn't change

	ports[port].value++;
	getPortName(port, pname);
	// Comprobamos rangos máximos y mínimos para los puertos de entrada analógicos
	if (ISINPUT(port) && ISANALOG(port)) {
		// Si el valor supera el limite impuesto, y esta subiendo, disparar alarma
		//i = port-DIGITALPORTS;
		if (abs(nv - ov) >= ports[port].range->tolerance) {
			getPortName(port, pname);
			printf(NOTICE, pname, (char *)" value ", int (nv));
		}

		if ((ports[port].range->alarmMax < 1024)
		    && (nv > ports[port].range->alarmMax) && (nv > ov)) {
			printf(ALARM, pname, (char *)" mxlvl ", nv);
			setPortValue(alarmport, HIGH);
		}
		if ((ports[port].range->warningMax < 1024)
		    && (nv > ports[port].range->warningMax) && (nv > ov)) {
			printf(WARNING, pname, (char *)" mxlvl ", nv);
		}
		if ((ports[port].range->alarmMin > 0)
		    && (nv < ports[port].range->alarmMin) && (nv < ov)) {
			printf(ALARM, pname, (char *)" mnlvl ", nv);
			setPortValue(alarmport, HIGH);
		}
		if ((ports[port].range->warningMin > 0)
		    && (nv < ports[port].range->warningMin) && (nv < ov)) {
			printf(WARNING, pname, (char *)" mnlvl ", nv);
		}
	}
	if (ISINPUT(port) && ISDIGITAL(port)) {
		// Si un puerto digital ha cambiado y tiene un temporizador asignado, le
		// asignamos al contador el valor correspondiente.
//      if (t_len[port]>0 && t_count[port]==0 && nv==HIGH) t_count[port] = t_len[port];
		if (nv == 1)
			printf(NOTICE, pname, (char *)" value ON   ");
		else
			printf(NOTICE, pname, (char *)" value OFF  ");
	}

	for (int i = 0; i < MAXLINKS; i++) {
		if (links[i][0] == port)	// Puerto enlazado
		{
			getPortName(links[i][1], pname);
			printf(NOTICE, pname, (char *)" trgrd");
			switch (links[i][2]) {
			case 'd':	// Directo: si A=1, B=1. Si A=0, B=0. Si A=65%, B=65%
				setPortValue(links[i][1], nv);
				break;
			case 'i':	// Inverso: si A=1, B=0. Si A=0, B=1. Si A=65%, B=35%
				if (nv == LOW)
					setPortValue(links[i][1], HIGH);
				else if (nv == HIGH)
					setPortValue(links[i][1], LOW);
				/// @todo Activate inverted link for analog ports
				//else
				//      setPortValue(links[i][1],255-nv);
				break;
			case 'p':	// Pulso: 0->1 o 1->0 cuando el puerto pasa a HIGH
				if (ISDIGITAL(port) && nv == HIGH) {
					if (ports[links[i][1]].value == LOW)
						setPortValue(links[i][1], HIGH);
					else
						setPortValue(links[i][1], LOW);
				}
				break;
			case 'f':	// Fall: 0->1 o 1->0 cuando el puerto pasa a LOW
				if (ISDIGITAL(port) && nv == LOW) {
					if (ports[links[i][1]].value == LOW)
						setPortValue(links[i][1], HIGH);
					else
						setPortValue(links[i][1], LOW);
				}
				break;

			default:
				printf(ERROR, (char *)"board ",
				       (char *)"error ", 304);
				printf(DEBUG, (char *)"lnktype ", links[i][2]);
				break;

			}
		}
	}
}

/** Set a value for the specified port
 \param i Port index (see \ref getPortId)
 \param value New value
 \return 0 if the value could be assigned, other if not.
*/
int setPortValue(int i, int value)
{
	char pname[6];
	//  if(i=-1) return -1;
	getPortName(i, pname);
	if (ISINPUT(i)) {
		printf(ERROR, pname, (char *)"error ", 107);
		return 1;
	}
	if (ISDIGITAL(i)) {
		digitalWrite(i, value);
		ports[i].value = value;
		if (value)
			printf(NOTICE, pname, (char *)" value ON");
		else
			printf(NOTICE, pname, (char *)" value OFF");
	} else if (ISANALOG(i)) {
		analogWrite(i, value);
		ports[i].value = value;
		printf(NOTICE, pname, (char *)" value ", int (value));
	} else if (ISVIRTUAL(i)) {
		triggerPortChange(i, LOW, HIGH);
	} else if (ISDISABLED(i)) {
		// Nothing. No warning neither
		return 0;
	} else {
		printf(ERROR, pname, (char *)"error ", 103);
		return 1;
	}

	printf(NOTICE, pname, (char *)" cmdok");
}

/** Set the type and configured characteristics to each port
*/
void resetPorts()
{
	int i;
	FOREACHPORT(i) {
		/// @todo Alta impedancia if type = "-"
		if (ISINPUT(i)) {
			pinMode(i, INPUT);
			digitalWrite(i, HIGH);	// Activating internal pull-up resistor
			if (ISDIGITAL(i))
				ports[i].value = digitalRead(i);
			if (ISANALOG(i))
				ports[i].value = 0;
//(analogRead(i-DIGITALPORTS) * 2.5 * ports[i].range->fxB / 1023 ) - ports[i].range->fxA;
		} else if (ISOUTPUT(i)) {
			digitalWrite(i, LOW);	// deactivating pull-up
			pinMode(i, OUTPUT);
		}
		if (ISPULSE(i)) {
			//pinMode(i, INPUT);
			pulseport = i;
			//noInterrupts();
#ifdef IR_H_
			if (i == 2)
				attachInterrupt(0, processIR, CHANGE);
			if (i == 3)
				attachInterrupt(1, processIR, CHANGE);
#endif
		}
		//! @todo Enable wake up with attachInterrupt(0, wakeUp, LOW);
	}

}

/** Store the specified name of the board in the EEPROM
 \param name New board name (5 Bytes)
 \return Always 0
*/
int saveBoardName(char *name)
{
	int i;
	for (i = 0; i < 5; i++) {
		EW(EMBOARDOFFSET + i, name[i]);
		bname[i] = name[i];
	}
	bname[5] = 0;
	return true;
}

/// Load the configuration from the EEPROM
int loadConfig()
{
	int x;
	int y;
	int dir;

	for (x = 0; x < 5; x++) {
		bname[x] = ER(EMBOARDOFFSET + x);
		if (bname[x] < 97 && bname[x] > 122)
			saveBoardName("domin");
	}
	bname[5] = 0;

	FOREACHPORT(x) {
		dir = int (EMPORTSOFFSET + x * EMPORTSLOT);
		ports[x].type = ER(dir + 5);
		if (!ISVALIDCHAR(ports[x].type))
			ports[x].type = 'x';
		if ((x >= DIGITALPORTS) && (x <= DIGITALPORTS + ANALOGPORTS)) {
			// For analog ports load RANGE
			ports[x].range->alarmMin = ERI(dir + 6) * 4;
			ports[x].range->warningMin = ERI(dir + 8) * 4;
			ports[x].range->warningMax = ERI(dir + 10) * 4;
			ports[x].range->alarmMax = ERI(dir + 12) * 4;
			ports[x].range->tolerance = ERI(dir + 14);
			// Conversion f(x) = A + Bx;
			ports[x].range->fxA = ERI(dir + 16);
			ports[x].range->fxB = ERI(dir + 18);
			// Si la placa no ha sido configurada, asignamos valores maximos
			if (ports[x].range->alarmMax == 0)
				ports[x].range->alarmMax = 1020;
			if (ports[x].range->warningMax == 0)
				ports[x].range->warningMax = 1020;
		} else {
			ports[x].range = NULL;
			if (ER(dir + 7) == 'a')
				alarmport = x;
		}
	}
	for (x = 0; x < MAXLINKS; x++) {
		links[x][0] = ER(EMLINKSOFFSET + x * 5);
		links[x][1] = ER(EMLINKSOFFSET + x * 5 + 1);
		links[x][2] = ER(EMLINKSOFFSET + x * 5 + 2);
		if (links[x][0] > TOTALPORTS)
			links[x][0] = 0;
		if (links[x][1] > TOTALPORTS)
			links[x][1] = 0;
		if (!ISVALIDCHAR(links[x][2]))
			links[x][2] = 'x';
	}
	return true;
}

/// Save the configuration into the EEPROM (see \ref EEPROM Map)
int saveConfig()
{
	int x;
	int y;
	int dir;
	byte b;

	FOREACHPORT(x) {
		dir = int (EMPORTSOFFSET + x * EMPORTSLOT);
		EW(dir + 5, ports[x].type);

		if ((x >= DIGITALPORTS) && (x <= DIGITALPORTS + ANALOGPORTS)) {
			// Over the "Digital" range, is analogic
			EW(dir + 6, int (ports[x].range->alarmMin / 4));
			EW(dir + 8, int (ports[x].range->warningMin / 4));
			EW(dir + 10, int (ports[x].range->warningMax / 4));
			EW(dir + 12, int (ports[x].range->alarmMax / 4));
			EW(dir + 14, ports[x].range->tolerance);
			EW(dir + 16, ports[x].range->fxA);
			EW(dir + 18, ports[x].range->fxB);
		} else {
			if (x == alarmport) {
				EW(dir + 7, 'a');
			} else {
				EW(dir + 7, ' ');
			}
		}
	}
	for (x = 0; x < MAXLINKS; x++) {
		EW(EMLINKSOFFSET + x * 5 + 0, links[x][0]);
		EW(EMLINKSOFFSET + x * 5 + 1, links[x][1]);
		EW(EMLINKSOFFSET + x * 5 + 2, links[x][2]);
		EW(EMLINKSOFFSET + x * 5 + 3, '-');
		EW(EMLINKSOFFSET + x * 5 + 4, '-');
	}

	// Writing the on-board test program (in future versions it will be programmable)
	byte prog[] = { 0,
		83, 8, 1,
		83, 21, 5,
		68, 21, 1,
		90, 21, 15,
		74, 7,
		83, 8, 0,
		0, 0, 0, 0, 0
	};
	for (x = 0; x < sizeof(prog); x++) {
		if (ER(EMFUNCSOFFSET + x) != prog[x])
			EW(EMFUNCSOFFSET + x, prog[x]);
	}

	return true;
}

/** Set a port's configuration (and name)
 \param id Port id
 \param cfg String with the configuration
 \param name New port name (optional)
 \return 0 if everything went right. Otherwise, -1
*/
int configPort(int id, char *cfg, char *name)
{
	int i;
	if (id < 0)
		return false;	// Ignoramos ID =-1 (puerto no encontrado)
	if (cfg != NULL) {
		/* Anulamos los rangos de alerta
		   emax[id]=100;
		   wmax[id]=100;
		   emin[id]=0;
		   wmin[id]=0;
		 */
		ports[id].type = 'X';
		for (i = 0; i < 5; i++) {
			if (cfg[i] == 'p') {
				pulseport = id;
				ports[id].type = 'P';
			}
			if (cfg[i] == 'A')
				alarmport = id;
			if (cfg[i] == 'd')
				ports[id].type = 'D';
			if (cfg[i] == 'a')
				ports[id].type = 'A';
			if (cfg[i] == 'i') {
				if (ports[id].type == 'D')
					ports[id].type = 'd';
				if (ports[id].type == 'A')
					ports[id].type = 'a';
				if (ports[id].type == 'P')
					ports[id].type = 'p';
			}
		}
	}
	printf(DEBUG, (char *)"ptype:", ports[id].type);

	if (name != NULL) {
		for (i = 0; i < 5; i++)
			EW(EMPORTSLOT * id + i, name[i]);
	}

	return true;
}

/** Set the Ethernet state
 \param state Status of the network: 1=ON, 0=OFF
*/
int ethSetState(byte state)
{
	EW(EMNETCFOFFSET, state);
	return true;
}

/** Load the default configuration.
 Useful if the EEPROM was empty or corrupted or before loading a template.
*/
int loadDefaultConfig()
{
	char pname[] = "empty";
	byte i;
	FOREACHPORT(i) {
		if (i >= DIGITALPORTS + ANALOGPORTS) {	// Virtual ports
			itoan(i - (DIGITALPORTS + ANALOGPORTS), pname, 5);
			pname[0] = 'v';
			configPort(i, (char *)"-", pname);
		} else if (i < DIGITALPORTS) {	// Digital ports
			// Evitamos configurar los puertos de red
			//if (network!=0 && (i!=11 && i!=12 && i!=13)) {
			itoan(i, pname, 5);
			pname[0] = 'd';
			pname[1] = 'o';
			configPort(i, (char *)"do", pname);
			//}
		} else {	// Analog:
			itoan(i - DIGITALPORTS, pname, 5);
			pname[0] = 'a';
			pname[1] = 'i';
			configPort(i, (char *)"-", pname);
			ports[i].range->alarmMin = 0;
			ports[i].range->warningMin = 0;
			ports[i].range->warningMax = 1023;
			ports[i].range->alarmMax = 1023;
			ports[i].range->tolerance = 1;
			// Conversion f(x) = A + Bx;
			ports[i].range->fxA = 0;
			ports[i].range->fxB = 1;
		}
	}
	FOREACHLINK(i) emptyLink(i);	// No links in default configuration

	configPort(0, (char *)"-", (char *)"usbrx");	// USB's RX
	configPort(1, (char *)"-", (char *)"usbtx");	// USB's TX
	ethSetState(0);
	return true;
}

/// This function reads the command from the serial port
int readFromSerialPort()
{
	byte b;
	//int i;
	// El dispositivo Arduino estara permanentemente iterando este bucle,
	// este o no conectado a traves del puerto USB. En el caso de que haya
	// conexion establecida, se tendra en cuenta.

	// Serial.available() devuelve el numero de bytes acumulados en el buffer.
	// Empleando un sistema de ancho fijo de los comandos facilitaremos en
	// gran medida el procesado
	while (Serial.available() > 0) {
		b = Serial.read();
		switch (b) {
		case -1:	// Null char
		case 0:	// Null char
		case 10:	// LF: No deberia suceder, salvo en consola emulada
			break;
		case 13:	// Final de linea
			output = SERIALPORT;
			processInstruction(instruction);
			inspos = 0;
			break;
		case 8:	// Backspace
			if (inspos > 0) {
				inspos--;
				if (echo == 1)
					Serial.print(b);
			}
			instruction[inspos] = 0;
			break;
		default:
			if (inspos < BUFFERSIZE) {
				if (echo == 1)
					Serial.print(b);
				instruction[inspos] = b;
				inspos++;
				instruction[inspos] = 0;
			} else {
				output = SERIALPORT;
				printf(ERROR, (char *)"board ",
				       (char *)"error ", 2);
			}
		}
	}
}

/// This function reads the command from a telnet port
int readFromTelnetPort()
{
	byte b;
	int i;
	Client client = server.available();
	if (client) {
		while (client.available() > 0) {
			b = client.read();
			switch (b) {
			case -1:	// Null char
			case 0:	// Null char
			case 33:	// !
			case 34:	// "
			case 35:	// #
			case 39:	// '
			case 10:	// LF: No deberia suceder, salvo en consola emulada
				break;
			case 4:	// EOT
			case 64:	// '@'
			case 127:	// DEL
				Serial.print("N:telnq=");
				Serial.println(b, DEC);
				client.stop();	// Close connection
				break;
			case 13:	// Final de linea
				output = TELNET;
				processInstruction(instruction);
				inspos = 0;
				Serial.print("N:telnq=");
				Serial.println(instruction);
				break;
			default:
				if ((inspos < BUFFERSIZE)
				    && (b >= 32 && b <= 126)) {
					instruction[inspos] = b;
					inspos++;
					instruction[inspos] = 0;
				} else {
					inspos = 0;
					Serial.print(b, DEC);
				}
			}
		}
		//client.stop();
	}
	return 0;
}

/// This function reads a command from the HTTP port
int readFromHTTPPort()
{
	byte b;
	int i;
	boolean finish = false;
	char query[BUFFERSIZE];

	Client wclient = webserver.available();

	if (wclient) {		// Only if there is a request, we continue
		query[0] = 0;	// clear the string
		i = 0;		// Point at the beginning of the string
		while (wclient.available() > 0) {
			b = wclient.read();
			switch (b) {
			case -1:	// Null char
			case 0:	// Null char
			case 10:	// LF: Should not happen!
				break;
			case 13:	// CR: end of line
				output = HTTP;
				wclient.println("HTTP/1.1 200 OK");
				wclient.println("Content-Type: text/html\n\n"
						"<html><head><title>Domino</title>"
						"<meta name='viewport' content='width=320;initial-scale=1.0;maximum-scale=1.0;user-scalable=0;'/>"
						"<link rel=stylesheet href='http://opendomo.org/files/domino.css' type='text/css'/>"
						"<script src='http://opendomo.org/files/domino.js'></script>"
						"</head><body>\n");
#ifdef MODULE_WEBSERVER
				processHTTPQuery(query, wclient);
#else
				wclient.println
				    ("<p>Web server module not enabled</p>");
#endif
				wclient.println("</body></html>");
				delay(10);
				wclient.flush();
				wclient.stop();
				Serial.print("N:httpq=");
				Serial.println(query);
				i = 0;
				break;
			default:
				if ((i < BUFFERSIZE) && (b >= 32 && b <= 126)) {
					query[i] = b;
					i++;
					query[i] = 0;
				}
				break;
			}
		}
		delay(10);
		wclient.flush();
		wclient.stop();
	}
	return 0;
}

/** Initializes the device.
 This function is called when the first power supply is connected.
*/
void setup()
{
	int i;
	instruction[0] = 0;

	FOREACHLINK(i) emptyLink(i);
	FOREACHPORT(i) {
		ports[i].changes = 0;	// Ponemos a cero los contadores
		if ((i >= DIGITALPORTS) && (i <= DIGITALPORTS + ANALOGPORTS)) {
			// For analog ports load RANGE
			ports[i].range = (struct portRanges *)
			    malloc(sizeof(struct portRanges));
		} else {
			ports[i].range = NULL;
		}
	}
	if (!loadConfig()) {	// Si la configuración es inválida cargamos la de por defecto
		printf(NOTICE, (char *)"error ", 21);
		if (loadDefaultConfig())
			printf(NOTICE, (char *)"cfdef");
	}

	Serial.begin(9600);
	printVersion();
	initNetwork();

	// Iniciamos los puertos
	resetPorts();

	printVersion();
}

/// Check if any port has changed its value, and trigger an event if required.
void refreshPortStatus()
{
	int i = 0;
	int k = 0;
	int val;
	char pname[6];

	loops++;
	if (loops >= 10)
		loops = 0;	// Cada 10 loops

// refresca el estado de los puertos cada X milisegundos
	if (millis() - time < 10)
		return;
	else
		time = millis();

	FOREACHPORT(i) {
		val = 0;

		if (ISOUTPUT(i)) {
			if (ISDIGITAL(i))
				val = digitalRead(i);
			else
				val = ports[i].value;
			if (ports[i].value != val)
				triggerPortChange(i, ports[i].value, val);
		} else if (ISINPUT(i)) {
			if (ISDIGITAL(i)) {
				// varias lecturas para minimizar problemas de ruido
				val = HIGH;
				for (k = 0; k < 3; k++) {
					if (digitalRead(i) != HIGH) {
						val = LOW;
						break;
					}
					delay(10);
				}

				if (ports[i].value == LOW && val == HIGH) {
					triggerPortChange(i, ports[i].value,
							  HIGH);
					ports[i].value = HIGH;
				} else if (ports[i].value == HIGH && val == LOW) {
					triggerPortChange(i, ports[i].value,
							  LOW);
					ports[i].value = LOW;
				}
			}

			if (ISANALOG(i)) {
				// Las entradas analogicas tienen un rango distinto
				val =
				    int ((2.5 *
					  (float)analogRead(i -
							    DIGITALPORTS) *
					  (float)ports[i].range->fxB) /
					 1023.0) - ports[i].range->fxA;
				//val=int(((float) analogRead(i-DIGITALPORTS) * (float) ports[i].range->fxB * 2.5 / 1023.0 ) - ports[i].range->fxA);
				triggerPortChange(i, ports[i].value, val);
				ports[i].value = val;
			}
			// Los puertos de entrada son susceptibles a sufrir ruido (verificar solo
			// durante los primeros 5s)
			if (loops == 0 && time < 5000) {
				if (ports[i].changes >= MAXCHANGES) {
					// puerto ruidoso si hubo mas de MAXCHANGES cambios por segundo
					ports[i].type = 'N';	// Desactivado
					getPortName(i, pname);
					printf(WARNING, pname,
					       (char *)".noisy");
				}
				ports[i].value = 0;	// Ponemos a cero los contadores
			}
		}
	}
}

/// Send the list of links created to the standard output
void listLinks()
{
	int i = 0;
	int a = 0;
	char pname1[6];
	char pname2[6];
	char lname[15];
	char type[] = "  ";

	for (i = 0; i < MAXLINKS; i++) {
		// Los "slots" vacíos se almacenan como enlace "0-0"
		if ((links[i][0] != 0) && (links[i][1] != 0)) {
			getPortName(links[i][0], pname1);
			getPortName(links[i][1], pname2);
			strncpy(lname, 5, pname1);
			strncat(lname, 15, 1, "-");
			strncat(lname, 15, 5, pname2);
			type[1] = links[i][2];
			printf(INFO, lname, type);
			a++;
		}
		// Formato de salida:
		// 01234567890123456789012345 (byte nº)
		// I:port1-port2 type
	}
	if (a == 0)
		printf(WARNING, (char *)"no links");
}

/// Send the list of ports to the standard output, including type and value
void listPorts()
{
	int i;
	int j;
	int perc;
	char type[8];
	char value[8];
	char pname[6];

	printf(NOTICE, (char *)"cmd ls");
	FOREACHPORT(i) {
		for (j = 0; j < 7; j++) {
			type[j] = '-';
			value[j] = ' ';
		}
		type[0] = ' ';
		type[6] = ' ';
		type[7] = 0;
		value[6] = 0;
		getPortName(i, pname);

		if (ISDIGITAL(i)) {
			type[1] = 'D';
			if (ISINPUT(i))
				type[2] = 'I';
			else
				type[2] = 'O';

			if (digitalRead(i) == HIGH) {
				value[0] = 'O';
				value[1] = 'N';
				value[2] = ' ';
			} else {
				value[0] = 'O';
				value[1] = 'F';
				value[2] = 'F';
			}
		} else if (ISANALOG(i)) {
			type[1] = 'A';
			itoan(ports[i].value, value, 5);
			if (ISINPUT(i)) {
				type[2] = 'I';
				value[5] = ' ';
			} else {
				type[2] = 'O';
				value[5] = ' ';
			}
		} else if (ISPULSE(i)) {
			type[1] = 'P';
			if (ISINPUT(i))
				type[2] = 'I';
			else
				type[2] = 'O';
		} else {
			type[1] = 'X';
		}

		printf(INFO, pname, type, value);
	}
}

/** Links two ports
 \param port1 Trigger port (usually input)
 \param port2 Triggered port (usually output)
 \return the link slot if the operation succeeded, otherwise -1 or -2 (invalid ports)
*/
int addLink(byte port1, byte port2)
{
	int l;
	if ((port1 == -1) || (port2 == -1))
		return -2;

	if ((ISINPUT(port1) && ISOUTPUT(port2)) || ISVIRTUAL(port1)) {
		FOREACHLINK(l) {
			if ((links[l][0] == 0) && (links[l][1] == 0))	// If the slot is empty
			{
				links[l][0] = port1;
				links[l][1] = port2;
				return l;
			}
			if ((links[l][0] == port1) && (links[l][1] == port2))	// the link already exists
			{
				return l;
			}
		}
	}
	return false;
}

/** Delete the link between two ports.
 \param port1 Trigger port
 \param port2 Triggered port
 \return 0 if everything went right, otherwise -1
*/
int delLink(byte port1, byte port2)
{
	int l;
	if ((port1 == -1) || (port2 == -1))
		return -2;
	FOREACHLINK(l) {
		if ((links[l][0] == port1) && (links[l][1] == port2)) {
			links[l][0] = 0;
			links[l][1] = 0;
			links[l][2] = 0;
			return 0;
		}
	}
	return -1;		// Enlace no encontrado
}

/** \page VPorts Virtual Ports
 The virtual ports are events that can be used as regular ports, linked to
 output ports to change their values to HIGH when the events start, bringing
 it to LOW when it's ended.

 The virtual ports can be used also as internal registers, storing the data
 that we want to preserve during the execution.

 All the possible uses for this ports will be developed in the near future.
*/

/** Create a virtual port
 \param name code for the port
 \return 0 if everything went right, otherwise -1
*/
int addVirtualPort(char *name)
{
/*	int i;
	FOREACHVPORT(i)
	{
		if (ports[i][0]==0) { // Empty! Add it here
			Serial.println("D:adding virtual port");
 			Serial.println(name);
			if (name[0]=='P') // First byte designates a pulse
			{
				vports[i][0]='P';
				vports[i][1]=h2d(name[1],name[2]);
				vports[i][2]=h2d(name[3],name[4]);
				vports[i][3]=h2d(name[5],name[6]);
				vports[i][4]=h2d(name[7],name[8]);
				vports[i][5]=0;
				vports[i][6]=0;
			}
			else
			{
				vports[i][0]=name[0];
				vports[i][1]=name[1];
				vports[i][2]=name[2];
				vports[i][3]=name[3];
	  			vports[i][4]=name[4];
				vports[i][6]=0;

			}
			printf(DEBUG,(char*)"vport added",i);
			return 0;
		}
		else
		{
			printf(DEBUG,(char*)"occuppied by",vports[i]);
			if (vports[i][0]==name[0]) { // Hey! it might be stored already!
				//! @todo Prevent storing repeated virtual ports
			}
		}
	}*/
	return false;
}

/** Get the specified port index
 \param label Name of the port
 \return The port's index, or -1 if not found
*/
int getPortId(char *label)
{
	int i;
	char pname[6];
	FOREACHPORT(i) {
		getPortName(i, pname);
		if (strcmp6(pname, label)) {
			return i;
		}
	}
	//! @todo get Virtual Port's ID
	printf(ERROR, (char *)"board ", (char *)"error ", 101);
	return -1;
}

/// Initialises the Ethernet Shield
int initNetwork()
{
	/** \page Ethernet Ethernet Shield
	If the first byte of the network configuration has the integer value "1",
	we will proceed to initialise the Ethernet shield and start the network
	services.

	Since Arduino's Ethernet library does not support DHCP, the IP address,
	net mask, gateway and MAC address will have to be manually assigned. To do
	so, it will be necessary to connect to the Arduino board by the USB port,
	and setting those values with the command "eth".

	Once configured and activated, the board can be unplugged and the shield
	can be placed on the main board. When the board is powered again, the
	Ethernet interface should be available and waiting for connection.

	The network service provided is a listening port (\ref LISTENINGPORT) that,
	once connected, will be used as the standard output.
	*/
	int i = 0;

	byte mac[6];
	byte ip[4];
	//! @todo: enable gateway, net mask and multicast address
	//byte gw[4];
	//byte mask[4];

	if (ER(EMNETCFOFFSET) == 1) {	// Si el byte esta a 1, arrancamos la red
		network = 1;
		delay(100);
		configPort(04, (char *)"-", (char *)"msdon");
		configPort(10, (char *)"-", (char *)"neton");
		configPort(11, (char *)"-", (char *)"netrx");
		configPort(12, (char *)"-", (char *)"nettx");
		configPort(13, (char *)"-", (char *)"netst");
		/// @todo The pins used by the ethernet shield should be disabled here
		EEPROM2string(EMNETCFOFFSET + 1, 6, mac);
		EEPROM2string(EMNETCFOFFSET + 7, 4, ip);
		Ethernet.begin(mac, ip);
		//EEPROM2string(EMNETCFOFFSET+10,4,gw);
		//EEPROM2string(EMNETCFOFFSET+14,4,mask);
		Serial.print((char *)"D:netmc:");
		for (i = 0; i < 6; i++)
			Serial.print(mac[i], HEX);
		Serial.println();
		Serial.print((char *)"D:netip:");
		for (i = 0; i < 4; i++)
			Serial.print(ip[i], DEC);
		Serial.println();
		server.begin();
		webserver.begin();
//    Ethernet.begin(mac,ip,gw,mask);
		// Network is configured
		Serial.println((char *)"N:nicok");
		return 0;
	} else {
		//Serial.println("I:Network NOT initialised");
		return -1;
	}
}

/** Save the MAC into the EEPROM
 \param macb1 Byte 1 of MAC Address
 \param macb2 Byte 2 of MAC Address
 \param macb3 Byte 3 of MAC Address
 \param macb4 Byte 4 of MAC Address
 \param macb5 Byte 5 of MAC Address
 \param macb6 Byte 6 of MAC Address
*/
int ethSetMAC(byte macb1, byte macb2, byte macb3,
	      byte macb4, byte macb5, byte macb6)
{
	EW(EMNETCFOFFSET + 1, macb1);
	EW(EMNETCFOFFSET + 2, macb2);
	EW(EMNETCFOFFSET + 3, macb3);
	EW(EMNETCFOFFSET + 4, macb4);
	EW(EMNETCFOFFSET + 5, macb5);
	EW(EMNETCFOFFSET + 6, macb6);
	return true;
}

/** Save the IP address into the EEPROM
 \param ipb1 Byte 1 of IP Address
 \param ipb2 Byte 2 of IP Address
 \param ipb3 Byte 3 of IP Address
 \param ipb4 Byte 4 of IP Address
*/
int ethSetIP(byte ipb1, byte ipb2, byte ipb3, byte ipb4)
{
	EW(EMNETCFOFFSET + 7, ipb1);
	EW(EMNETCFOFFSET + 8, ipb2);
	EW(EMNETCFOFFSET + 9, ipb3);
	EW(EMNETCFOFFSET + 10, ipb4);
	return true;
}

/** Save the IP address of the gateway into the EEPROM
 \param ipb1 Byte 1 of IP Address
 \param ipb2 Byte 2 of IP Address
 \param ipb3 Byte 3 of IP Address
 \param ipb4 Byte 4 of IP Address
*/
int ethSetGW(byte ipb1, byte ipb2, byte ipb3, byte ipb4)
{
	EW(EMNETCFOFFSET + 11, ipb1);
	EW(EMNETCFOFFSET + 12, ipb2);
	EW(EMNETCFOFFSET + 13, ipb3);
	EW(EMNETCFOFFSET + 14, ipb4);
	return true;
}

/** Save the network mask into the EEPROM
 \param ipb1 Byte 1 of mask
 \param ipb2 Byte 2 of mask
 \param ipb3 Byte 3 of mask
 \param ipb4 Byte 4 of mask
*/
int ethSetNetmask(byte ipb1, byte ipb2, byte ipb3, byte ipb4)
{
	EW(EMNETCFOFFSET + 15, ipb1);
	EW(EMNETCFOFFSET + 16, ipb2);
	EW(EMNETCFOFFSET + 17, ipb3);
	EW(EMNETCFOFFSET + 18, ipb4);
	return true;
}

/** Sub-procedure for dealing with Ethernet board
 \param cmd Command
 \return
*/
int ethControl(const char *cmd)
{
	if (cmd[4] == 'u' && cmd[5] == 'p') {	// Activating ethernet
		return ethSetState(1);
	} else if (cmd[4] == 's' && cmd[5] == 't') {	// Status of the ethernet
		printf(INFO, (char *)".ethip ");
		printf(INFO, (char *)".ethmc ");
		printf(INFO, (char *)".ethmk ");
		printf(INFO, (char *)".ethgw ");
		return true;
	} else if (cmd[4] == 'm' && cmd[5] == 'c') {	// setting MAC address
		return ethSetMAC(h2d(cmd[7], cmd[8]), h2d(cmd[9], cmd[10]),
				 h2d(cmd[11], cmd[12]), h2d(cmd[13], cmd[14]),
				 10, 10);
	} else if (cmd[4] == 'i' && cmd[5] == 'p') {	// setting IP
		return ethSetIP(h2d(cmd[7], cmd[8]), h2d(cmd[9], cmd[10]),
				h2d(cmd[11], cmd[12]), h2d(cmd[13], cmd[14]));
	} else if (cmd[4] == 'g' && cmd[5] == 'w') {	// setting gateway
//      ethSetGW(h2d(larg[0],larg[1]), h2d(larg[2],larg[3]),\
//              h2d(larg[4],larg[5]), h2d(larg[6],larg[7]));
	} else if (cmd[4] == 'n' && cmd[5] == 'm') {	// setting netmask
//      ethSetNetmask(h2d(larg[0],larg[1]), h2d(larg[2],larg[3]),\
//              h2d(larg[4],larg[5]), h2d(larg[6],larg[7]));
	} else if (cmd[4] == 'o' && cmd[5] == 'f') {	// deactivating ethernet
		return ethSetState(0);
	} else if (cmd[4] == 't' && cmd[5] == 'e') {	// testing ethernet
		// Serial.println("connecting...");
	}
	return false;
}

/** Print the contents of EEPROM (serial port only). See \ref EEPROM Map
*/
void printMap()
{
	int i;
	int j;
	int p;
	int b;

	Serial.print("0");
	for (i = 0; i < EMSEGMENTS; i++)	// For each segment in EEPROM
	{
		if (i * j < 260)
			Serial.print("0");
		Serial.print(i * j, HEX);
		Serial.print(":");
		if (i * j + 20 < 260)
			Serial.print("0");
		Serial.print(i * j + 19, HEX);
		Serial.print(" | ");
		for (j = 0; j < 20; j++) {
			p = i * 20 + j;
			b = ER(p);
			if ((b > 32) && (b < 127))
				Serial.print(b, BYTE);
			else
				Serial.print(".");
			// Cada 5 Bytes un separador
			if (((j + 1) % 5) == 0)
				Serial.print(" ");
		}
		Serial.print(" | ");
		for (j = 0; j < 20; j++) {
			p = i * 20 + j;
			b = ER(p);
			if (b < 16)
				Serial.print("0");
			Serial.print(b, HEX);
			// Cada 5 Bytes un separador
			if (((j + 1) % 5) == 0)
				Serial.print(" ");
		}
		Serial.println();
	}
}

/** \page Func On-Board functions
 Domino can store configurable functions in the configuration space.
 This routines are stored in the \ref EMFUNCSOFFSET and are processed
 by \ref executeNextInstruction, one instruction in every cycle of the
 main loop.

 The commands supported are encoded in one Byte per instruction:
 - \ref CMD_END
 - \ref CMD_SET
 - \ref CMD_DECREASE
 - \ref CMD_INCREASE
 - \ref CMD_JMP
 - \ref CMD_JMP_IF_ISZERO
 - \ref CMD_JMP_IF_NOTZERO
*/

#define CMD_END 0		//!< Exit the program
#define CMD_SET 83		//!< Set the value of the port
#define CMD_DECREASE 68		//!< Decrease the value of the port
#define CMD_INCREASE 73		//!< Increase the value of the port
#define CMD_JMP 74		//!< Jump to another instruction
#define CMD_JMP_IF_ISZERO 90	//!< Jump if port value is zero
#define CMD_JMP_IF_NOTZERO 78	//!< Jump if port value is not zero
/// Executes the next instruction of the \ref Func, if it exists
void executeNextInstruction()
{
	byte ins;
	byte arg1;
	byte arg2;

	if (regP == 0)
		return;
	ins = ER(EMFUNCSOFFSET + regP);
	arg1 = ER(EMFUNCSOFFSET + regP + 1);
	arg2 = ER(EMFUNCSOFFSET + regP + 2);
	printf(DEBUG, (char *)"inst= ", ins);
	printf(DEBUG, (char *)"arg1=", arg1);
	printf(DEBUG, (char *)"arg2=", arg2);
	switch (ins) {
	case CMD_END:
		regP = 0;
		return;
		break;
	case CMD_SET:
		setPortValue(arg1, arg2);
		regP += 3;
		break;
	case CMD_DECREASE:
		if (ports[arg1].value > 0)
			setPortValue(arg1, ports[arg1].value - arg2);
		regP += 3;
		break;
	case CMD_INCREASE:
		if (ports[arg1].value < 255)
			setPortValue(arg1, ports[arg1].value + arg2);
		regP += 3;
		break;
	case CMD_JMP:
		regP = arg1;
		break;
	case CMD_JMP_IF_ISZERO:
		if (ports[arg1].value == 0)
			regP = arg2;
		else
			regP += 3;	// otherwise, go to the next instruction
		break;
	case CMD_JMP_IF_NOTZERO:
		if (ports[arg1].value != 0)
			regP = arg2;
		else
			regP += 3;	// otherwise, go to the next instruction
		break;
	default:
		break;
	}

}

/** Executes the specified command
 \param cmd The command string (max 20Bytes)
 \return 0 if the operation was successful, other if not
*/
int processInstruction(const char *cmd)
{
	int i;
	int j;
	char arg1[6];		// First argument (if specified)
	char arg2[6];		// Second argument (if specified)
	char larg[BUFFERSIZE - 5];	// Bytes from 5-20, both arguments and space
	int value = 0;
	/* All the commands will follow a similar structure:
	 * 012345678901234 <-byte no.
	 cmd arg01 arg02
	 cfg portn di
	 lbl portn alias
	 lnk portn portm
	 wmx portn value
	 wmn portn value
	 set portn value
	 */
	printf(DEBUG, (char *)"cmdB=", (char *)cmd);
	GETARG1(cmd, arg1);
	GETARG2(cmd, arg2);
	printf(DEBUG, (char *)"arg1=", arg1);
	printf(DEBUG, (char *)"arg2=", arg2);
	printf(DEBUG, (char *)"cmdA=", (char *)cmd);

	/// Supported commands:
	if (SPELL3(cmd, 'l', 's', 't'))	/// - lst: List configured ports
	{
		listPorts();
		return 0;
	} else if (SPELL3(cmd, 'e', 'c', 'h'))	/// - echo: (de)activates local echo
	{
		if (echo == 0) {
			printf(NOTICE, (char *)"echo on");
			echo = 1;
		} else {
			printf(NOTICE, (char *)"echo off");
			echo = 0;
		}
		return 0;
	} else if (SPELL3(cmd, 'd', 'e', 'b'))	/// - debug: (de)activates debug mode
	{
		if (debug == 0) {
			printf(NOTICE, (char *)"debug on");
			debug = 1;
		} else {
			printf(NOTICE, (char *)"debug off");
			debug = 0;
		}
		return 0;
	} else if (SPELL3(cmd, 'd', 'e', 'f'))	/// - default: load default configuration
	{
		loadDefaultConfig();
		resetPorts();
		printf(NOTICE, (char *)"board ", (char *)"cfdef");
	} else if (SPELL3(cmd, 'l', 'o', 'a'))	/// - load: load stored configuration
	{
		if (loadConfig())
			printf(NOTICE, (char *)"board ", (char *)"cfloa");
		else
			printf(ERROR, (char *)"board ", (char *)"error ", 21);
		return 0;
	} else if (SPELL3(cmd, 's', 'a', 'v'))	/// - save: save configuration to EEPROM
	{
		if (saveConfig())
			printf(NOTICE, (char *)"cmdok");
		else
			printf(ERROR, (char *)"board ", (char *)"error ", 22);
		return 0;
	} else if (SPELL3(cmd, 'c', 'f', 'g'))	/// - cfg: configures the specified port
	{
		i = getPortId(arg1);
		if (configPort(i, arg2, NULL))
			printf(NOTICE, (char *)"cfgok");
		resetPorts();
	} else if (SPELL3(cmd, 'l', 'b', 'l'))	/// - lbl: set an alias to a port
	{
		i = getPortId(arg1);
		if (configPort(i, NULL, arg2))
			printf(NOTICE, (char *)"cfgok");
	} else if (SPELL3(cmd, 'r', 'e', 's'))	/// - reset: clear the EEPROM
	{
		for (i = 0; i < EMSEGMENTS * EMPORTSLOT; i++)
			EW(i, 255);
		printf(NOTICE, (char *)"memcl");
	} else if (SPELL3(cmd, 'm', 'a', 'p'))	/// - map: print the \ref EEPROM
	{
		printMap();
	} else if (SPELL3(cmd, 'p', 'u', 't'))	/// - put: changes a Byte in the \ref EEPROM
	{
		i = atoi(arg1);
		j = atoi(arg2);
		if (i > 0 && i < EMSEGMENTS * EMPORTSLOT && j >= 0 && j <= 255) {
			EW(i, j);
			printf(NOTICE, (char *)"cmdok");
		} else
			printf(ERROR, (char *)"board ", (char *)"error ", 30);
	} else if (SPELL3(cmd, 'v', 'e', 'r'))	/// - ver: display the current version
	{
		printVersion();
	} else if (SPELL3(cmd, 's', 'n', 'm'))	/// - snm: set a new name for the board
	{
		saveBoardName(arg1);
		printf(NOTICE, (char *)"cmdok");
	} else if (SPELL3(cmd, 'w', 'm', 'x'))	/// - wmx: set max warning for a analog port
	{
		i = getPortId(arg1);
		ports[i].range->warningMax = atoi(arg2);
		printf(NOTICE, arg1, (char *)".wnmax=",
		       ports[i].range->warningMax);
	} else if (SPELL3(cmd, 'w', 'm', 'n'))	/// - wmn: set min warning for a analog port
	{
		i = getPortId(arg1);
		ports[i].range->warningMin = atoi(arg2);
		printf(NOTICE, arg1, (char *)".wnmin=",
		       ports[i].range->warningMin);
	} else if (SPELL3(cmd, 'a', 'm', 'x'))	/// - amx: set max alarm for a analog port
	{
		i = getPortId(arg1);
		ports[i].range->alarmMax = atoi(arg2);
		printf(NOTICE, arg1, (char *)".almax=",
		       ports[i].range->alarmMax);
	} else if (SPELL3(cmd, 'a', 'm', 'n'))	/// - amn: set min alarm for a analog port
	{
		i = getPortId(arg1);
		ports[i].range->alarmMin = atoi(arg2);
		printf(NOTICE, arg1, (char *)".almin=",
		       ports[i].range->alarmMin);
	} else if (SPELL3(cmd, 't', 'o', 'l'))	/// - tol: set tolerance for an analog port
	{
		i = getPortId(arg1);
		ports[i].range->tolerance = atoi(arg2);
		printf(NOTICE, arg1, (char *)".toler=",
		       ports[i].range->tolerance);
	} else if (SPELL3(cmd, 'r', 'n', 'g'))	/// - rng: show ranges for an analog port
	{
		i = getPortId(arg1);
		printf(INFO, arg1, (char *)".almin=", ports[i].range->alarmMin);
		printf(INFO, arg1, (char *)".wnmin=",
		       ports[i].range->warningMin);
		printf(INFO, arg1, (char *)".wnmax=",
		       ports[i].range->warningMax);
		printf(INFO, arg1, (char *)".almax=", ports[i].range->alarmMax);
		printf(INFO, arg1, (char *)".toler=",
		       ports[i].range->tolerance);
	} else if (SPELL3(cmd, 'f', 'x', 'a'))	/// - fxa: assign the A-value of the \ref conversion
	{
		i = getPortId(arg1);
		ports[i].range->fxA = atoi(arg2);
		printf(NOTICE, arg1, (char *)".cmdok", ports[i].range->fxA);
	} else if (SPELL3(cmd, 'f', 'x', 'b'))	/// - fxb: assign the B-value of the \ref conversion
	{
		i = getPortId(arg1);
		ports[i].range->fxB = atoi(arg2);
		printf(NOTICE, arg1, (char *)".cmdok", ports[i].range->fxB);
	} else if (SPELL3(cmd, 's', 'e', 't'))	/// - set: set a value to an output port
	{
		i = getPortId(arg1);
		if (arg2[1] == 'n')
			setPortValue(i, HIGH);
		else if (arg2[1] == 'f')
			setPortValue(i, LOW);
		else
			setPortValue(i, atoi(arg2));
	} else if (SPELL3(cmd, 'l', 'n', 'k'))	/// - lnk: link two ports
	{
		i = getPortId(arg1);
		j = getPortId(arg2);
		lastlink = addLink(i, j);
		if (lastlink >= 0)
			printf(NOTICE, arg1, (char *)".cmdok");
		else
			printf(ERROR, (char *)"board ", (char *)"error ", 306);
		return 0;
	} else if (SPELL3(cmd, 'u', 'n', 'l'))	/// - unl: unlink two ports
	{
		i = getPortId(arg1);
		j = getPortId(arg2);
		if (delLink(i, j) == 0)
			printf(NOTICE, (char *)" cmdok");
		return 0;
	} else if (SPELL3(cmd, 'h', 'i', 'b'))	/// - hib: hibernate
	{
		printf(NOTICE, (char *)"hibernating");
		delay(100);
		goToSleep();
	} else if (SPELL3(cmd, 'l', 'l', 'n'))	/// - lln: list the current links
	{
		listLinks();
		return 0;
	} else if (SPELL3(cmd, 'l', 'c', 'f'))	/// - lcf: configure the specified link
	{
		i = lastlink;
		if (i < MAXLINKS) {
			printf(DEBUG, (char *)"configuring link", i);
			links[i][2] = arg1[0];
			return 0;
		} else {
			printf(ERROR, (char *)"board ", (char *)"error ", 305);
			return -1;
		}
	} else if (SPELL3(cmd, 'u', 'p', 't'))	/// - upt: print uptime
	{
		printf(INFO, (char *)"upsec=", seconds);
		printf(INFO, (char *)"upday=", days);
	} else if (SPELL3(cmd, 'm', 'e', 'm'))	/// - mem: print available memory
	{
		i = memoryTest();
		printf(INFO, (char *)"frmem=", i);
		printf(INFO, (char *)"objsz=", sizeof(ports));
	} else if (SPELL3(cmd, 'e', 'x', 'e'))	/// - exe: call sub-procedure
	{
		regP = atoi(arg1);
		printf(NOTICE, (char *)"procd ", regP);
	} else if (SPELL3(cmd, 't', 's', 't'))	/// - tst: perform a board test
	{
		printf(INFO, (char *)"port=", 14);
		printf(INFO, (char *)"x=   ", analogRead(14 - DIGITALPORTS));
		printf(INFO, (char *)"VREFxVAL", int (2.5 * 230));
		printf(INFO, (char *)"VREFxVALxFACTOR= ", int (2.5 * 230 * 51));
		printf(INFO, (char *)"VREFxVALxFACTOR/MAX",
		       int (2.5 * 230 * 51 / 1023));
		printf(INFO, (char *)"fxA= ", ports[14].range->fxA);
		printf(INFO, (char *)"fxB= ", ports[14].range->fxB);
		//val=int(((float) analogRead(i-DIGITALPORTS) * (float) ports[i].range->fxB * 2.5 / 1023.0 ) - ports[i].range->fxA);
		printf(INFO, (char *)"part1=", (char *)"(8 C)",
		       (230 * ports[14].range->fxB * 2.5 / 1023) -
		       ports[14].range->fxA);
		printf(INFO, (char *)"part1=", (char *)"(23 C)",
		       int (352 * 51 * 2.5 / 1023) - 20);
	} else if (SPELL3(cmd, 'e', 't', 'h'))	/// - eth: ethernet functions
	{
		if (ethControl(cmd))
			printf(NOTICE, (char *)"cmdok");
		else
			printf(ERROR, (char *)"board ", (char *)"error ", 440);
	} else if (SPELL3(cmd, 's', 'u', 'b'))	/// - sub: create a subroutine
	{
#ifdef MODULE_ROUTINES
		if (createSubRoutine(arg1))
			printf(NOTICE, (char *)"cmdok");
		else
			printf(ERROR, (char *)"error ", 445);
#else
		printf(ERROR, (char *)"unimplemented");
#endif
	} else {
		printf(ERROR, (char *)"board ", (char *)"error ", 1);
		printf(DEBUG, (char *)" cmd received:", (char *)cmd);
		return 1;
	}
}

#ifdef MODULE_XXX
#include <xxx.h>
#endif

/** Permanent loop.
 This is the main function of Arduino. This function is called again when
 the last call is ended.
*/
void loop()
{
	// Leemos las marcas de tiempo y actualizamos valores
	time = millis() / 1000;
	if (seconds != (time % DAYLENGHT)) {	// Solamente actualizamos si ha cambiado
		seconds = time % DAYLENGHT;
		if (seconds == 0)
			days++;
	}

	readFromSerialPort();

#ifdef MODULE_XXX
	const char *modname = "MODULE XXX";
	xxx_func();
#endif

	// Si la red esta activada, comprobamos puertos
	if (network != 0) {
		readFromTelnetPort();
		readFromHTTPPort();
	}
	// Aparte de comprobar los comandos recibidos, miramos si
	// hay algun cambio de estado en los puertos.
	refreshPortStatus();

	// Si hay alguna dirección en regP, ejecutamos
	if (regP > 0)
		executeNextInstruction();

	// Tiempo de espera
	delay(DELAYCYCLE);
}

#ifdef NO_PDE
int main()
{
	setup();
	for (;;)
		loop();
	return 0;
}
#endif

